﻿## 8.16. Составные типы

Составной тип представляет структуру табличной строки или записи; по сути это просто список имён полей и соответствующих типов данных. Postgres Pro позволяет использовать составные типы во многом так же, как и простые типы. Например, в определении таблицы можно объявить столбец составного типа.

### 8.16.1. Объявление составных типов
Ниже приведены два простых примера определения составных типов:

    CREATE TYPE complex AS (
        r       double precision,
        i       double precision
    );

    CREATE TYPE inventory_item AS (
        name            text,
        supplier_id     integer,
        price           numeric
    );
Синтаксис очень похож на CREATE TABLE, за исключением того, что он допускает только названия полей и их типы, какие-либо ограничения (такие как NOT NULL) в настоящее время не поддерживаются. Заметьте, что ключевое слово AS здесь имеет значение; без него система будет считать, что подразумевается другой тип команды CREATE TYPE, и выдаст неожиданную синтаксическую ошибку.

Определив такие типы, мы можем использовать их в таблицах:

    CREATE TABLE on_hand (
        item      inventory_item,
        count     integer
    );

    INSERT INTO on_hand VALUES (ROW('fuzzy dice', 42, 1.99), 1000);
или функциях:

    CREATE FUNCTION price_extension(inventory_item, integer) RETURNS numeric
    AS 'SELECT $1.price * $2' LANGUAGE SQL;

    SELECT price_extension(item, 10) FROM on_hand;
Всякий раз, когда создаётся таблица, вместе с ней автоматически создаётся составной тип. Этот тип представляет тип строки таблицы, и его именем становится имя таблицы. Например, при выполнении команды:

    CREATE TABLE inventory_item (
        name            text,
        supplier_id     integer REFERENCES suppliers,
        price           numeric CHECK (price > 0)
    );
в качестве побочного эффекта будет создан составной тип inventory_item, в точности соответствующий тому, что был показан выше, и использовать его можно так же. Однако заметьте, что в текущей реализации есть один недостаток: так как с составным типом не могут быть связаны ограничения, то описанные в определении таблицы ограничения не применяются к значениям составного типа вне таблицы. (Чтобы обойти этот недостаток, создайте домен поверх составного типа и добавьте желаемые ограничения в виде ограничений CHECK для данного домена.)

### 8.16.2. Конструирование составных значений
Чтобы записать значение составного типа в виде текстовой константы, его поля нужно заключить в круглые скобки и разделить их запятыми. Значение любого поля можно заключить в кавычки, а если оно содержит запятые или скобки, это делать обязательно. (Подробнее об этом говорится ниже.) Таким образом, в общем виде константа составного типа записывается так:

    '( значение1 , значение2 , ... )'
Например, эта запись:

    '("fuzzy dice",42,1.99)'
будет допустимой для описанного выше типа inventory_item. Чтобы присвоить NULL одному из полей, в соответствующем месте в списке нужно оставить пустое место. Например, эта константа задаёт значение NULL для третьего поля:

    '("fuzzy dice",42,)'
Если же вместо NULL требуется вставить пустую строку, нужно записать пару кавычек:

    '("",42,)'
Здесь в первом поле окажется пустая строка, а в третьем — NULL.

(Такого рода константы массивов на самом деле представляют собой всего лишь частный случай констант, описанных в Подразделе 4.1.2.7. Константа изначально воспринимается как строка и передаётся процедуре преобразования составного типа. При этом может потребоваться явно указать тип, к которому будет приведена константа.)

Значения составных типов также можно конструировать, используя синтаксис выражения ROW. В большинстве случаев это значительно проще, чем записывать значения в строке, так как при этом не нужно беспокоиться о вложенности кавычек. Мы уже обсуждали этот метод ранее:

    ROW('fuzzy dice', 42, 1.99)
    ROW('', 42, NULL)
Ключевое слово ROW на самом деле может быть необязательным, если в выражении определяются несколько полей, так что эту запись можно упростить до:

    ('fuzzy dice', 42, 1.99)
    ('', 42, NULL)
Синтаксис выражения ROW более подробно рассматривается в Подразделе 4.2.13.

### 8.16.3. Обращение к составным типам
Чтобы обратиться к полю столбца составного типа, после имени столбца нужно добавить точку и имя поля, подобно тому, как указывается столбец после имени таблицы. На самом деле, эти обращения неотличимы, так что часто бывает необходимо использовать скобки, чтобы команда была разобрана правильно. Например, можно попытаться выбрать поле столбца из тестовой таблицы on_hand таким образом:

    SELECT item.name FROM on_hand WHERE item.price > 9.99;
Но это не будет работать, так как согласно правилам SQL имя item здесь воспринимается как имя таблицы, а не столбца в таблице on_hand. Поэтому этот запрос нужно переписать так:

    SELECT (item).name FROM on_hand WHERE (item).price > 9.99;
либо указать также и имя таблицы (например, в запросе с многими таблицами), примерно так:

    SELECT (on_hand.item).name FROM on_hand WHERE (on_hand.item).price > 9.99;
В результате объект в скобках будет правильно интерпретирован как ссылка на столбец item, из которого выбирается поле.

При выборке поля из значения составного типа также возможны подобные синтаксические казусы. Например, чтобы выбрать одно поле из результата функции, возвращающей составное значение, потребуется написать что-то подобное:

    SELECT (my_func(...)).field FROM ...
Без дополнительных скобок в этом запросе произойдёт синтаксическая ошибка.

Специальное имя поля * означает «все поля»; подробнее об этом рассказывается в Подразделе 8.16.5.

### 8.16.4. Изменение составных типов
Ниже приведены примеры правильных команд добавления и изменения значений составных столбцов. Первые команды иллюстрируют добавление или изменение всего столбца:

    INSERT INTO mytab (complex_col) VALUES((1.1,2.2));

    UPDATE mytab SET complex_col = ROW(1.1,2.2) WHERE ...;
В первом примере опущено ключевое слово ROW, а во втором оно есть; присутствовать или отсутствовать оно может в обоих случаях.

Мы можем изменить также отдельное поле составного столбца:

    UPDATE mytab SET complex_col.r = (complex_col).r + 1 WHERE ...;
Заметьте, что при этом не нужно (и на самом деле даже нельзя) заключать в скобки имя столбца, следующее сразу за предложением SET, но в ссылке на тот же столбец в выражении, находящемся по правую сторону знака равенства, скобки обязательны.

И мы также можем указать поля в качестве цели команды INSERT:

    INSERT INTO mytab (complex_col.r, complex_col.i) VALUES(1.1, 2.2);
Если при этом мы не укажем значения для всех полей столбца, оставшиеся поля будут заполнены значениями NULL.

### 8.16.5. Использование составных типов в запросах
С составными типами в запросах связаны особые правила синтаксиса и поведение. Эти правила образуют полезные конструкции, но они могут быть неочевидными, если не понимать стоящую за ними логику.

В Postgres Pro ссылка на имя таблицы (или её псевдоним) в запросе по сути является ссылкой на составное значение текущей строки в этой таблице. Например, имея таблицу inventory_item, показанную выше, мы можем написать:

    SELECT c FROM inventory_item c;
Этот запрос выдаёт один столбец с составным значением, и его результат может быть таким:

            c
    ------------------------
    ("fuzzy dice",42,1.99)
    (1 row)
Заметьте, однако, что простые имена сопоставляются сначала с именами столбцов, и только потом с именами таблиц, так что такой результат получается только потому, что в таблицах запроса не оказалось столбца с именем c.

Обычную запись полного имени столбца вида имя_таблицы.имя_столбца можно понимать как применение выбора поля к составному значению текущей строки таблицы. (Из соображений эффективности на самом деле это реализовано по-другому.)

Когда мы пишем

    SELECT c.* FROM inventory_item c;
то, согласно стандарту SQL, мы должны получить содержимое таблицы, развёрнутое в отдельные столбцы:

        name    | supplier_id | price
    ------------+-------------+-------
    fuzzy dice |          42 |  1.99
    (1 row)
    как с запросом

    SELECT c.name, c.supplier_id, c.price FROM inventory_item c;
Postgres Pro применяет такое развёртывание для любых выражений с составными значениями, хотя как показано выше, необходимо заключить в скобки значение, к которому применяется .*, если только это не простое имя таблицы. Например, если myfunc() — функция, возвращающая составной тип со столбцами a, b и c, то эти два запроса выдадут одинаковый результат:

    SELECT (myfunc(x)).* FROM some_table;
    SELECT (myfunc(x)).a, (myfunc(x)).b, (myfunc(x)).c FROM some_table;
>Подсказка
Postgres Pro осуществляет развёртывание столбцов фактически переводя первую форму во вторую. Таким образом, в данном примере myfunc() будет вызываться три раза для каждой строки и с одним, и с другим синтаксисом. Если это дорогостоящая функция, и вы хотите избежать лишних вызовов, можно использовать такой запрос:

    SELECT m.* FROM some_table, LATERAL myfunc(x) AS m;
Размещение вызова функции в элементе FROM LATERAL гарантирует, что она будет вызываться для строки не более одного раза. Конструкция m.* так же разворачивается в m.a, m.b, m.c, но теперь эти переменные просто ссылаются на выходные значения FROM. (Ключевое слово LATERAL здесь является необязательным, но мы добавили его, чтобы подчеркнуть, что функция получает x из some_table.)

Запись составное_значение.* приводит к такому развёртыванию столбцов, когда она фигурирует на верхнем уровне выходного списка SELECT, в списке RETURNING команд INSERT/UPDATE/DELETE, в предложении VALUES или в конструкторе строки. Во всех других контекстах (включая вложенные в одну из этих конструкций), добавление .* к составному значению не меняет это значение, так как это воспринимается как «все столбцы» и поэтому выдаётся то же составное значение. Например, если функция somefunc() принимает в качестве аргумента составное значение, эти запросы равносильны:

    SELECT somefunc(c.*) FROM inventory_item c;
    SELECT somefunc(c) FROM inventory_item c;
В обоих случаях текущая строка таблицы inventory_item передаётся функции как один аргумент с составным значением. И хотя дополнение .* в этих случаях не играет роли, использовать его считается хорошим стилем, так как это ясно указывает на использование составного значения. В частности анализатор запроса воспримет c в записи c.* как ссылку на имя или псевдоним таблицы, а не имя столбца, что избавляет от неоднозначности; тогда как без .* неясно, означает ли c имя таблицы или имя столбца, и на самом деле при наличии столбца с именем c будет выбрано второе прочтение.

Эту концепцию демонстрирует и следующий пример, все запросы в котором действуют одинаково:

    SELECT * FROM inventory_item c ORDER BY c;
    SELECT * FROM inventory_item c ORDER BY c.*;
    SELECT * FROM inventory_item c ORDER BY ROW(c.*);
Все эти предложения ORDER BY обращаются к составному значению строки, вследствие чего строки сортируются по правилам, описанным в Подразделе 9.23.6. Однако, если в inventory_item содержится столбец с именем c, первый запрос будет отличаться от других, так как в нём выполнится сортировка только по данному столбцу. С показанными выше именами столбцов предыдущим запросам также равнозначны следующие:

    SELECT * FROM inventory_item c ORDER BY ROW(c.name, c.supplier_id, c.price);
    SELECT * FROM inventory_item c ORDER BY (c.name, c.supplier_id, c.price);
(В последнем случае используется конструктор строки, в котором опущено ключевое слово ROW.)

Другая особенность синтаксиса, связанная с составными значениями, состоит в том, что мы можем использовать функциональную запись для извлечения поля составного значения. Это легко можно объяснить тем, что записи поле(таблица) и таблица.поле взаимозаменяемы. Например, следующие запросы равнозначны:

    SELECT c.name FROM inventory_item c WHERE c.price > 1000;
    SELECT name(c) FROM inventory_item c WHERE price(c) > 1000;
Более того, если у нас есть функция, принимающая один аргумент составного типа, мы можем вызвать её в любой записи. Все эти запросы равносильны:

    SELECT somefunc(c) FROM inventory_item c;
    SELECT somefunc(c.*) FROM inventory_item c;
    SELECT c.somefunc FROM inventory_item c;
Эта равнозначность записи с полем и функциональной записи позволяет использовать с составными типами функции, реализующие «вычисляемые поля». При этом приложению, использующему последний из предыдущих запросов, не нужно знать, что фактически somefunc — не настоящий столбец таблицы.

>Подсказка
Учитывая такое поведение, будет неразумно давать функции, принимающей один аргумент составного типа, то же имя, что и одному из полей данного составного типа. В случае неоднозначности прочтение имени поля будет выбрано при использовании синтаксиса обращения к полю, а прочтение имени функции — если используется синтаксис вызова функции. Однако в PostgreSQL до 11 версии всегда выбиралось прочтение имени поля, если только синтаксис вызова не подталкивал к прочтению имени функции. Чтобы принудительно выбрать прочтение имени функции, в предыдущих версиях надо было дополнить это имя схемой, то есть написать схема.функция(составное_значение).

### 8.16.6. Синтаксис вводимых и выводимых значений составного типа
Внешнее текстовое представление составного значения состоит из записи элементов, интерпретируемых по правилам ввода/вывода для соответствующих типов полей, и оформления структуры составного типа. Оформление состоит из круглых скобок (( и )) окружающих всё значение, и запятых (,) между его элементами. Пробельные символы вне скобок игнорируются, но внутри они считаются частью соответствующего элемента и могут учитываться или не учитываться в зависимости от правил преобразования вводимых данных для типа этого элемента. Например, в записи:

    '(  42)'
пробелы будут игнорироваться, если соответствующее поле имеет целочисленный тип, но не текстовый.

Как было показано ранее, записывая составное значение, любой его элемент можно заключить в кавычки. Это нужно делать, если при разборе этого значения без кавычек возможна неоднозначность. Например, в кавычки нужно заключать элементы, содержащие скобки, кавычки, запятую или обратную косую черту. Чтобы включить в поле составного значения, заключённое в кавычки, такие символы, как кавычки или обратная косая черта, перед ними нужно добавить обратную косую черту. (Кроме того, продублированные кавычки в значении поля, заключённого в кавычки, воспринимаются как одинарные, подобно апострофам в строках SQL.) С другой стороны, можно обойтись без кавычек, защитив все символы в данных, которые могут быть восприняты как часть синтаксиса составного значения, с помощью спецпоследовательностей.

Значение NULL в этой записи представляется пустым местом (когда между запятыми или скобками нет никаких символов). Чтобы ввести именно пустую строку, а не NULL, нужно написать "".

Функция вывода составного значения заключает значения полей в кавычки, если они представляют собой пустые строки, либо содержат скобки, запятые, кавычки или обратную косую черту, либо состоят из одних пробелов. (В последнем случае можно обойтись без кавычек, но они добавляются для удобочитаемости.) Кавычки и обратная косая черта, заключённые в значения полей, при выводе дублируются.

>Примечание
Помните, что написанная SQL-команда прежде всего интерпретируется как текстовая строка, а затем как составное значение. Вследствие этого число символов обратной косой черты удваивается (если используются спецпоследовательности). Например, чтобы ввести в поле составного столбца значение типа text с обратной косой чертой и кавычками, команду нужно будет записать так:

    INSERT ... VALUES ('("\"\\")');
Сначала обработчик спецпоследовательностей удаляет один уровень обратной косой черты, так что анализатор составного значения получает на вход ("\"\\"). В свою очередь, он передаёт эту строку процедуре ввода значения типа text, где она преобразуются в "\. (Если бы мы работали с типом данных, процедура ввода которого также интерпретирует обратную косую черту особым образом, например bytea, нам могло бы понадобиться уже восемь таких символов, чтобы сохранить этот символ в поле составного значения.) Во избежание такого дублирования спецсимволов строки можно заключать в доллары (см. Подраздел 4.1.2.4).

>Подсказка
Записывать составные значения в командах SQL часто бывает удобнее с помощью конструктора ROW. В ROW отдельные значения элементов записываются так же, как если бы они не были членами составного выражения.

## 8.17. Диапазонные типы

Диапазонные типы представляют диапазоны значений некоторого типа данных (он также называется подтипом диапазона). Например, диапазон типа timestamp может представлять временной интервал, когда зарезервирован зал заседаний. В данном случае типом данных будет tsrange (сокращение от «timestamp range»), а подтипом — timestamp. Подтип должен быть полностью упорядочиваемым, чтобы можно было однозначно определить, где находится значение по отношению к диапазону: внутри, до или после него.

Диапазонные типы полезны тем, что позволяют представить множество возможных значений в одной структуре данных и чётко выразить такие понятия, как пересечение диапазонов. Наиболее очевидный вариант их использования — применять диапазоны даты и времени для составления расписания, но также полезными могут оказаться диапазоны цен, интервалы измерений и т. д.

### 8.17.1. Встроенные диапазонные типы
Postgres Pro имеет следующие встроенные диапазонные типы:

int4range — диапазон подтипа integer

int8range — диапазон подтипа bigint

numrange — диапазон подтипа numeric

tsrange — диапазон подтипа timestamp without time zone

tstzrange — диапазон подтипа timestamp with time zone

daterange — диапазон подтипа date

Помимо этого, вы можете определять собственные типы; подробнее это описано в CREATE TYPE.

### 8.17.2. Примеры
    CREATE TABLE reservation (room int, during tsrange);
    INSERT INTO reservation VALUES
        (1108, '[2010-01-01 14:30, 2010-01-01 15:30)');

    -- Вхождение
    SELECT int4range(10, 20) @> 3;

    -- Перекрытие
    SELECT numrange(11.1, 22.2) && numrange(20.0, 30.0);

    -- Получение верхней границы
    SELECT upper(int8range(15, 25));

    -- Вычисление пересечения
    SELECT int4range(10, 20) * int4range(15, 25);

    -- Является ли диапазон пустым?
    SELECT isempty(numrange(1, 5));
    Полный список операторов и функций, предназначенных для диапазонных типов, приведён в Таблице 9.53 и Таблице 9.54.

### 8.17.3. Включение и исключение границ
Любой непустой диапазон имеет две границы, верхнюю и нижнюю, и включает все точки между этими значениями. В него также может входить точка, лежащая на границе, если диапазон включает эту границу. И наоборот, если диапазон не включает границу, считается, что точка, лежащая на этой границе, в него не входит.

В текстовой записи диапазона включение нижней границы обозначается символом «[», а исключением — символом «(». Для верхней границы включение обозначается аналогично, символом «]», а исключение — символом «)». (Подробнее это описано в Подразделе 8.17.5.)

Для проверки, включается ли нижняя или верхняя граница в диапазон, предназначены функции lower_inc и upper_inc, соответственно.

### 8.17.4. Неограниченные (бесконечные) диапазоны
Нижнюю границу диапазона можно опустить и определить тем самым диапазон, включающий все значения, лежащие ниже верхней границы, например: (,3]. Подобным образом, если не определить верхнюю границу, в диапазон войдут все значения, лежащие выше нижней границы. Если же опущена и нижняя, и верхняя границы, такой диапазон будет включать все возможные значения своего подтипа. Указание отсутствующей границы как включаемой в диапазон автоматически преобразуется в исключающее; например, [,] преобразуется в (,). Можно воспринимать отсутствующие значения как плюс/минус бесконечность, но всё же это особые значения диапазонного типа, которые охватывают и возможные для подтипа значения плюс/минус бесконечность.

Для подтипов, в которых есть понятие «бесконечность», infinity может использоваться в качестве явного значения границы. При этом, например, в диапазон [today,infinity) с подтипом timestamp не будет входить специальное значение infinity данного подтипа, однако это значение будет входить в диапазон [today,infinity], как и в диапазоны [today,) и [today,].

Проверить, определена ли верхняя или нижняя граница, можно с помощью функций lower_inf и upper_inf, соответственно.

### 8.17.5. Ввод/вывод диапазонов
Вводимое значение диапазона должно записываться в одной из следующих форм:

    (нижняя-граница,верхняя-граница)
    (нижняя-граница,верхняя-граница]
    [нижняя-граница,верхняя-граница)
    [нижняя-граница,верхняя-граница]
    empty
Тип скобок (квадратные или круглые) определяет, включаются ли в диапазон соответствующие границы, как описано выше. Заметьте, что последняя форма содержит только слово empty и определяет пустой диапазон (диапазон, не содержащий точек).

Здесь нижняя-граница может быть строкой с допустимым значением подтипа или быть пустой (тогда диапазон будет без нижней границы). Аналогично, верхняя-граница может задаваться одним из значений подтипа или быть неопределённой (пустой).

Любое значение диапазона можно заключить в кавычки ("). А если значение содержит круглые или квадратные скобки, запятые, кавычки или обратную косую черту, использовать кавычки необходимо, чтобы эти символы не рассматривались как часть синтаксиса диапазона. Чтобы включить в значение диапазона, заключённое в кавычки, такие символы, как кавычки или обратная косая черта, перед ними нужно добавить обратную косую черту. (Кроме того, продублированные кавычки в значении диапазона, заключённого в кавычки, воспринимаются как одинарные, подобно апострофам в строках SQL.) С другой стороны, можно обойтись без кавычек, защитив все символы в данных, которые могут быть восприняты как часть синтаксиса диапазона, с помощью спецпоследовательностей. Чтобы задать в качестве границы пустую строку, нужно ввести "", так как пустая строка без кавычек будет означать отсутствие границы.

Пробельные символы до и после определения диапазона игнорируются, но когда они присутствуют внутри скобок, они воспринимаются как часть значения верхней или нижней границы. (Хотя они могут также игнорироваться в зависимости от подтипа диапазона.)

>Примечание
Эти правила очень похожи на правила записи значений для полей составных типов. Дополнительные замечания приведены в Подразделе 8.16.6.

Примеры:

    -- в диапазон включается 3, не включается 7 и включаются все точки между ними
    SELECT '[3,7)'::int4range;

    -- в диапазон не включаются 3 и 7, но включаются все точки между ними
    SELECT '(3,7)'::int4range;

    -- в диапазон включается только одно значение 4
    SELECT '[4,4]'::int4range;

    -- диапазон не включает никаких точек (нормализация заменит его определение
    --  на 'empty')
    SELECT '[4,4)'::int4range;
### 8.17.6. Конструирование диапазонов
Для каждого диапазонного типа определена функция конструктора, имеющая то же имя, что и данный тип. Использовать этот конструктор обычно удобнее, чем записывать текстовую константу диапазона, так как это избавляет от потребности в дополнительных кавычках. Функция конструктора может принимать два или три параметра. Вариант с двумя параметрами создаёт диапазон в стандартной форме (нижняя граница включается, верхняя исключается), тогда как для варианта с тремя параметрами включение границ определяется третьим параметром. Третий параметр должен содержать одну из строк: «()», «(]», «[)» или «[]». Например:

    -- Полная форма: нижняя граница, верхняя граница и текстовая строка, определяющая
    -- включение/исключение границ.
    SELECT numrange(1.0, 14.0, '(]');

    -- Если третий аргумент опущен, подразумевается '[)'.
    SELECT numrange(1.0, 14.0);

    -- Хотя здесь указывается '(]', при выводе значение будет приведено к
    -- каноническому виду, так как int8range — тип дискретного диапазона (см. ниже).
    SELECT int8range(1, 14, '(]');

    -- Когда вместо любой границы указывается NULL, соответствующей границы
    --  у диапазона не будет.
    SELECT numrange(NULL, 2.2);
### 8.17.7. Типы дискретных диапазонов
Дискретным диапазоном считается диапазон, для подтипа которого однозначно определён «шаг», как например для типов integer и date. Значения этих двух типов можно назвать соседними, когда между ними нет никаких других значений. В непрерывных диапазонах, напротив, всегда (или почти всегда) можно найти ещё одно значение между двумя данными. Например, непрерывным диапазоном будет диапазон с подтипами numeric и timestamp. (Хотя timestamp имеет ограниченную точность, то есть теоретически он является дискретным, но всё же лучше считать его непрерывным, так как шаг его обычно не определён.)

Можно также считать дискретным подтип диапазона, в котором чётко определены понятия «следующего» и «предыдущего» элемента для каждого значения. Такие определения позволяют преобразовывать границы диапазона из включаемых в исключаемые, выбирая следующий или предыдущий элемент вместо заданного значения. Например, диапазоны целочисленного типа [4,8] и (3,9) описывают одно и то же множество значений; но для диапазона подтипа numeric это не так.

Для типа дискретного диапазона определяется функция канонизации, учитывающая размер шага для данного подтипа. Задача этой функции — преобразовать равнозначные диапазоны к единственному представлению, в частности нормализовать включаемые и исключаемые границы. Если функция канонизации не определена, диапазоны с различным определением будут всегда считаться разными, даже когда они на самом деле представляют одно множество значений.

Для встроенных типов int4range, int8range и daterange каноническое представление включает нижнюю границу и не включает верхнюю; то есть диапазон приводится к виду [). Однако для нестандартных типов можно использовать и другие соглашения.

### 8.17.8. Определение новых диапазонных типов
Пользователи могут определять собственные диапазонные типы. Это может быть полезно, когда нужно использовать диапазоны с подтипами, для которых нет встроенных диапазонных типов. Например, можно определить новый тип диапазона для подтипа float8:

    CREATE TYPE floatrange AS RANGE (
        subtype = float8,
        subtype_diff = float8mi
    );

    SELECT '[1.234, 5.678]'::floatrange;
Так как для float8 осмысленное значение «шага» не определено, функция канонизации в данном примере не задаётся.

Определяя собственный диапазонный тип, вы также можете выбрать другие правила сортировки или класс оператора B-дерева для его подтипа, что позволит изменить порядок значений, от которого зависит, какие значения попадают в заданный диапазон.

Если подтип можно рассматривать как дискретный, а не непрерывный, в команде CREATE TYPE следует также задать функцию канонизации. Этой функции будет передаваться значение диапазона, а она должна вернуть равнозначное значение, но, возможно, с другими границами и форматированием. Для двух диапазонов, представляющих одно множество значений, например, целочисленные диапазоны [1, 7] и [1, 8), функция канонизации должна выдавать один результат. Какое именно представление будет считаться каноническим, не имеет значения — главное, чтобы два равнозначных диапазона, отформатированных по-разному, всегда преобразовывались в одно значение с одинаковым форматированием. Помимо исправления формата включаемых/исключаемых границ, функция канонизации может округлять значения границ, если размер шага превышает точность хранения подтипа. Например, в типе диапазона для подтипа timestamp можно определить размер шага, равный часу, тогда функция канонизации должна будет округлить границы, заданные, например с точностью до минут, либо вместо этого выдать ошибку.

Помимо этого, для любого диапазонного типа, ориентированного на использование с индексами GiST или SP-GiST, должна быть определена разница значений подтипов, функция subtype_diff. (Индекс сможет работать и без subtype_diff, но в большинстве случаев это будет не так эффективно.) Эта функция принимает на вход два значения подтипа и возвращает их разницу (т. е. X минус Y) в значении типа float8. В показанном выше примере может использоваться функция float8mi, определяющая нижележащую реализацию обычного оператора «минус» для типа float8, но для другого подтипа могут потребоваться дополнительные преобразования. Иногда для представления разницы в числовом виде требуется ещё и творческий подход. Функция subtype_diff, насколько это возможно, должна быть согласована с порядком сортировки, вытекающим из выбранных правил сортировки и класса оператора; то есть, её результат должен быть положительным, если согласно порядку сортировки первый её аргумент больше второго.

Ещё один, не столь тривиальный пример функции subtype_diff:

    CREATE FUNCTION time_subtype_diff(x time, y time) RETURNS float8 AS
    'SELECT EXTRACT(EPOCH FROM (x - y))' LANGUAGE sql STRICT IMMUTABLE;

    CREATE TYPE timerange AS RANGE (
        subtype = time,
        subtype_diff = time_subtype_diff
    );

    SELECT '[11:10, 23:00]'::timerange;
Дополнительные сведения о создании диапазонных типов можно найти в описании CREATE TYPE.

### 8.17.9. Индексация
Для столбцов, имеющих диапазонный тип, можно создать индексы GiST и SP-GiST. Например, так создаётся индекс GiST:

    CREATE INDEX reservation_idx ON reservation USING GIST (during);
Индекс GiST или SP-GiST помогает ускорить запросы со следующими операторами: =, &&, <@, @>, <<, >>, -|-, &< и &> (дополнительно о них можно узнать в Таблице 9.53.

Кроме того, для таких столбцов можно создать индексы на основе хеша и B-деревьев. Для индексов таких типов полезен по сути только один оператор диапазона — равно. Порядок сортировки B-дерева определяется для значений диапазона соответствующими операторами < и >, но этот порядок может быть произвольным и он не очень важен в реальном мире. Поддержка B-деревьев и хешей диапазонными типами нужна в основном для сортировки и хеширования при выполнении запросов, но не для создания самих индексов.

### 8.17.10. Ограничения для диапазонов
Тогда как для скалярных значений естественным ограничением является UNIQUE, оно обычно не подходит для диапазонных типов. Вместо этого чаще оказываются полезнее ограничения-исключения (см. CREATE TABLE ... CONSTRAINT ... EXCLUDE). Такие ограничения позволяют, например определить условие «непересечения» диапазонов. Например:

    CREATE TABLE reservation (
        during tsrange,
        EXCLUDE USING GIST (during WITH &&)
    );
Это ограничение не позволит одновременно сохранить в таблице несколько диапазонов, которые накладываются друг на друга:

    INSERT INTO reservation VALUES
        ('[2010-01-01 11:30, 2010-01-01 15:00)');
    INSERT 0 1

    INSERT INTO reservation VALUES
        ('[2010-01-01 14:45, 2010-01-01 15:45)');
    ОШИБКА:  конфликтующее значение ключа нарушает ограничение-исключение "reservation_during_excl"
    ПОДРОБНОСТИ: Ключ (during)=(["2010-01-01 14:45:00","2010-01-01 15:45:00"))
    конфликтует с существующим ключом (during)=(["2010-01-01 11:30:00","2010-01-01 15:00:00"))
Для максимальной гибкости в ограничении-исключении можно сочетать простые скалярные типы данных с диапазонами, используя расширение btree_gist. Например, если btree_gist установлено, следующее ограничение не будет допускать пересекающиеся диапазоны, только если совпадают также и номера комнат:

    CREATE EXTENSION btree_gist;
    CREATE TABLE room_reservation (
        room text,
        during tsrange,
        EXCLUDE USING GIST (room WITH =, during WITH &&)
    );

    INSERT INTO room_reservation VALUES
        ('123A', '[2010-01-01 14:00, 2010-01-01 15:00)');
    INSERT 0 1

    INSERT INTO room_reservation VALUES
        ('123A', '[2010-01-01 14:30, 2010-01-01 15:30)');
    ОШИБКА:  конфликтующее значение ключа нарушает ограничение-исключение
    "room_reservation_room_during_excl"
    ПОДРОБНОСТИ:  Ключ (room, during)=(123A, [ 2010-01-01 14:30:00,
    2010-01-01 15:30:00 )) конфликтует
    с существующим ключом (room, during)=(123A, ["2010-01-01 14:00:00","2010-01-01 15:00:00")).

    INSERT INTO room_reservation VALUES
        ('123B', '[2010-01-01 14:30, 2010-01-01 15:30)');
    INSERT 0 1

## 8.18. Типы доменов
Домен — пользовательский тип данных, основанный на другом нижележащем типе. Он может быть определён с условиями, ограничивающими множество допустимых значений подмножеством значений нижележащего типа. В остальном он ведёт себя как нижележащий тип — например, с доменными типом будут работать любые операторы или функции, применимые к нижележащему типу. Нижележащим типом может быть любой встроенный или пользовательский базовый тип, тип-перечисление, массив, составной тип, диапазон или другой домен.

Например, мы можем создать домен поверх целых чисел, принимающий только положительные числа:

    CREATE DOMAIN posint AS integer CHECK (VALUE > 0);
    CREATE TABLE mytable (id posint);
    INSERT INTO mytable VALUES(1);   -- работает
    INSERT INTO mytable VALUES(-1);  -- ошибка
Когда к значению доменного типа применяются операторы или функции, предназначенные для нижележащего типа, домен автоматически приводится к нижележащему типу. Так, например, результат операции mytable.id - 1 будет считаться имеющим тип integer, а не posint. Мы могли бы записать (mytable.id - 1)::posint, чтобы снова привести результат к типу posint, что повлечёт перепроверку ограничений домена. В этом случае, если данное выражение будет применено к id, равному 1, произойдёт ошибка. Значение нижележащего типа можно присвоить полю или переменной доменного типа, не записывая приведение явно, но и в этом случае ограничения домена будут проверяться.

За дополнительными сведениями обратитесь к описанию CREATE DOMAIN.

## 8.19. Идентификаторы объектов
Идентификатор объекта (Object Identifier, OID) используется внутри Postgres Pro в качестве первичного ключа различных системных таблиц. Идентификатор объекта представляется в типе oid. Также для типа oid определены следующие псевдонимы: regproc, regprocedure, regoper, regoperator, regclass, regtype, regrole, regnamespace, regconfig и regdictionary. Обзор этих типов приведён в Таблице 8.26.

В настоящее время тип oid реализован как четырёхбайтное целое. Таким образом, значение этого типа может быть недостаточно большим для обеспечения уникальности в базе данных или даже в отдельных больших таблицах.

Для самого типа oid помимо сравнения определены всего несколько операторов. Однако его можно привести к целому и затем задействовать в обычных целочисленных вычислениях. (При этом следует опасаться путаницы со знаковыми/беззнаковыми значениями.)

Типы-псевдонимы OID сами по себе не вводят новых операций и отличаются только специализированными функциями ввода/вывода. Эти функции могут принимать и выводить не просто числовые значения, как тип oid, а символические имена системных объектов. Эти типы позволяют упростить поиск объектов по значениям OID. Например, чтобы выбрать из pg_attribute строки, относящиеся к таблице mytable, можно написать:

    SELECT * FROM pg_attribute WHERE attrelid = 'mytable'::regclass;
вместо:

    SELECT * FROM pg_attribute
    WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'mytable');
Хотя второй вариант выглядит не таким уж плохим, но это лишь очень простой запрос. Если же потребуется выбрать правильный OID, когда таблица mytable есть в нескольких схемах, вложенный подзапрос будет гораздо сложнее. Преобразователь вводимого значения типа regclass находит таблицу согласно заданному пути поиска схем, так что он делает «всё правильно» автоматически. Аналогично, приведя идентификатор таблицы к типу regclass, можно получить символическое представление числового кода.

Таблица 8.26. Идентификаторы объектов

| Имя                                      | Ссылки                                   | Описание                       | Пример значения                          |
|------------------------------------------|------------------------------------------|--------------------------------|------------------------------------------|
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">oid</code> | any                                      | числовой идентификатор объекта | <code class="literal" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">564182</code> |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">regproc</code> | <code class="structname" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_proc</code> | имя функции                    | <code class="literal" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">sum</code> |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">regprocedure</code> | <code class="structname" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_proc</code> | функция с типами аргументов    | <code class="literal" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">sum(int4)</code> |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">regoper</code> | <code class="structname" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_operator</code> | имя оператора                  | <code class="literal" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">+</code> |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">regoperator</code> | <code class="structname" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_operator</code> | оператор с типами аргументов   | <code class="literal" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">*(integer,integer)</code><span> </span>или<span> </span><code class="literal" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">-(NONE,integer)</code> |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">regclass</code> | <code class="structname" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_class</code> | имя отношения                  | <code class="literal" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_type</code> |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">regtype</code> | <code class="structname" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_type</code> | имя типа данных                | <code class="literal" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">integer</code> |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">regrole</code> | <code class="structname" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_authid</code> | имя роли                       | <code class="literal" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">smithee</code> |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">regnamespace</code> | <code class="structname" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_namespace</code> | пространство имён              | <code class="literal" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_catalog</code> |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">regconfig</code> | <code class="structname" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_ts_config</code> | конфигурация текстового поиска | <code class="literal" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">english</code> |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">regdictionary</code> | <code class="structname" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_ts_dict</code> | словарь текстового поиска      | <code class="literal" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">simple</code> |

Все типы псевдонимов OID для объектов, сгруппированных в пространство имён, принимают имена, дополненные именем схемы, и выводят имена со схемой, если данный объект нельзя будет найти в текущем пути поиска без имени схемы. Типы regproc и regoper принимают только уникальные вводимые имена (не перегруженные), что ограничивает их применимость; в большинстве случаев лучше использовать regprocedure или regoperator. Для типа regoperator в записи унарного оператора неиспользуемый операнд заменяется словом NONE.

Дополнительным свойством большинства типов псевдонимов OID является образование зависимостей. Когда в сохранённом выражении фигурирует константа одного из этих типов (например, в представлении или в значении столбца по умолчанию), это создаёт зависимость от целевого объекта. Например, если значение по умолчанию определяется выражением nextval('my_seq'::regclass), Postgres Pro понимает, что это выражение зависит от последовательности my_seq, и не позволит удалить последовательность раньше, чем будет удалено это выражение. Единственным ограничением является тип regrole. Константы этого типа в таких выражениях не допускаются.

>Примечание
Типы псевдонимов OID не полностью следуют правилам изоляции транзакций. Планировщик тоже воспринимает их как простые константы, что может привести к неоптимальному планированию запросов.

Есть ещё один тип системных идентификаторов, xid, представляющий идентификатор транзакции (сокращённо xact). Этот тип имеют системные столбцы xmin и xmax. Идентификаторы транзакций определяются 32-битными числами.

Третий тип идентификаторов, используемых в системе, — cid, идентификатор команды (command identifier). Этот тип данных имеют системные столбцы cmin и cmax. Идентификаторы команд — это тоже 32-битные числа.

И наконец, последний тип системных идентификаторов — tid, идентификатор строки/кортежа (tuple identifier). Этот тип данных имеет системный столбец ctid. Идентификатор кортежа представляет собой пару (из номера блока и индекса кортежа в блоке), идентифицирующую физическое расположение строки в таблице.

(Подробнее о системных столбцах рассказывается в Разделе 5.5.)

## 8.20. Тип pg_lsn
Тип данных pg_lsn может применяться для хранения значения LSN (последовательный номер в журнале, Log Sequence Number), которое представляет собой указатель на позицию в журнале WAL. Этот тип содержит XLogRecPtr и является внутренним системным типом Postgres Pro.

Технически LSN — это 64-битное целое, представляющее байтовое смещение в потоке журнала предзаписи. Он выводится в виде двух шестнадцатеричных чисел до 8 цифр каждое, через косую черту, например: 16/B374D848. Тип pg_lsn поддерживает стандартные операторы сравнения, такие как = и >. Можно также вычесть один LSN из другого с помощью оператора -; результатом будет число байт между этими двумя позициями в журнале предзаписи.

## 8.21. Псевдотипы
В систему типов Postgres Pro включены несколько специальных элементов, которые в совокупности называются псевдотипами. Псевдотип нельзя использовать в качестве типа данных столбца, но можно объявить функцию с аргументом или результатом такого типа. Каждый из существующих псевдотипов полезен в ситуациях, когда характер функции не позволяет просто получить или вернуть определённый тип данных SQL. Все существующие псевдотипы перечислены в Таблице 8.27.

Таблица 8.27. Псевдотипы

| Имя                                      | Описание                                 |
|------------------------------------------|------------------------------------------|
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">any</code> | Указывает, что функция принимает любой вводимый тип данных. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">anyelement</code> | Указывает, что функция принимает любой тип данных (см.<span> </span><a class="xref" href="https://postgrespro.ru/docs/postgrespro/12/extend-type-system#EXTEND-TYPES-POLYMORPHIC" title="36.2.5. Полиморфные типы" style="box-sizing: border-box; background-color: transparent; color: rgb(0, 145, 208); text-decoration: underline; cursor: url(&quot;&quot;) 14 0, auto !important;">Подраздел 36.2.5</a>). |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">anyarray</code> | Указывает, что функция принимает любой тип массива (см.<span> </span><a class="xref" href="https://postgrespro.ru/docs/postgrespro/12/extend-type-system#EXTEND-TYPES-POLYMORPHIC" title="36.2.5. Полиморфные типы" style="box-sizing: border-box; background-color: transparent; color: rgb(0, 145, 208); text-decoration: underline; cursor: url(&quot;&quot;) 14 0, auto !important;">Подраздел 36.2.5</a>). |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">anynonarray</code> | Указывает, что функция принимает любой тип данных, кроме массивов (см.<span> </span><a class="xref" href="https://postgrespro.ru/docs/postgrespro/12/extend-type-system#EXTEND-TYPES-POLYMORPHIC" title="36.2.5. Полиморфные типы" style="box-sizing: border-box; background-color: transparent; color: rgb(0, 145, 208); text-decoration: underline; cursor: url(&quot;&quot;) 14 0, auto !important;">Подраздел 36.2.5</a>). |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">anyenum</code> | Указывает, что функция принимает любое перечисление (см.<span> </span><a class="xref" href="https://postgrespro.ru/docs/postgrespro/12/extend-type-system#EXTEND-TYPES-POLYMORPHIC" title="36.2.5. Полиморфные типы" style="box-sizing: border-box; background-color: transparent; color: rgb(0, 145, 208); text-decoration: underline; cursor: url(&quot;&quot;) 14 0, auto !important;">Подраздел 36.2.5</a><span> </span>и<span> </span><a class="xref" href="https://postgrespro.ru/docs/postgrespro/12/datatype-enum" title="8.7. Типы перечислений" style="box-sizing: border-box; background-color: transparent; color: rgb(0, 145, 208); text-decoration: underline; cursor: url(&quot;&quot;) 14 0, auto !important;">Раздел 8.7</a>). |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">anyrange</code> | Указывает, что функция принимает любой диапазонный тип данных (см.<span> </span><a class="xref" href="https://postgrespro.ru/docs/postgrespro/12/extend-type-system#EXTEND-TYPES-POLYMORPHIC" title="36.2.5. Полиморфные типы" style="box-sizing: border-box; background-color: transparent; color: rgb(0, 145, 208); text-decoration: underline; cursor: url(&quot;&quot;) 14 0, auto !important;">Подраздел 36.2.5</a><span> </span>и<span> </span><a class="xref" href="https://postgrespro.ru/docs/postgrespro/12/rangetypes" title="8.17. Диапазонные типы" style="box-sizing: border-box; background-color: transparent; color: rgb(0, 145, 208); text-decoration: underline; cursor: url(&quot;&quot;) 14 0, auto !important;">Раздел 8.17</a>). |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">cstring</code> | Указывает, что функция принимает или возвращает строку в стиле C. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">internal</code> | Указывает, что функция принимает или возвращает внутренний серверный тип данных. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">language_handler</code> | Обработчик процедурного языка объявляется как возвращающий тип<span> </span><code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">language_handler</code>. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">fdw_handler</code> | Обработчик обёртки сторонних данных объявляется как возвращающий тип<span> </span><code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">fdw_handler</code>. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">index_am_handler</code> | Обработчик метода доступа индекса объявляется как возвращающий тип<span> </span><code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">index_am_handler</code>. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">tsm_handler</code> | Обработчик метода выборки из таблицы объявляется как возвращающий тип<span> </span><code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">tsm_handler</code>. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">record</code> | Указывает, что функция принимает или возвращает неопределённый тип строки. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">trigger</code> | Триггерная функция объявляется как возвращающая тип<span> </span><code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">trigger</code>. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">event_trigger</code> | Функция событийного триггера объявляется как возвращающая тип<span> </span><code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">event_trigger</code>. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">pg_ddl_command</code> | Обозначает представление команд DDL, доступное событийным триггерам. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">void</code> | Указывает, что функция не возвращает значение. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">unknown</code> | Обозначает ещё не распознанный тип, то есть раскрытое строковое значение. |
| <code class="type" style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em; padding: 0px; color: rgb(82, 95, 108); background-color: rgb(255, 255, 255); border-radius: 0px;">opaque</code> | Устаревший тип, который раньше использовался во многих вышеперечисленных случаях. |

Функции, написанные на языке C (встроенные или динамически загружаемые), могут быть объявлены с параметрами или результатами любого из этих типов. Ответственность за безопасное поведение функции с аргументами таких типов ложится на разработчика функции.

Функции, написанные на процедурных языках, могут использовать псевдотипы, только если это позволяет соответствующий язык. В настоящее время большинство процедурных языков запрещают использовать псевдотипы в качестве типа аргумента и позволяют использовать для результатов только типы void и record (и trigger или event_trigger, когда функция реализует триггер или событийный триггер). Некоторые языки также поддерживают полиморфные функции с типами anyelement, anyarray, anynonarray, anyenum и anyrange.

Псевдотип internal используется в объявлениях функций, предназначенных только для внутреннего использования в СУБД, но не для прямого вызова в запросах SQL. Если у функции есть как хотя бы один аргумент типа internal, её нельзя будет вызывать из SQL. Чтобы сохранить типобезопасность при таком ограничении, следуйте важному правилу: не создавайте функцию, возвращающую результат типа internal, если у неё нет ни одного аргумента internal.